#include "singleticketwidget.h"
#include "ui_singleticketwidget.h"

#include "ticket.h"
#include "ticketinterface.h"
#include "ticketchangeset.h"
#include "ticketexplorer.h"

#include "informationwidget.h"

#include <QtCore/qmath.h>
#include <QtCore/QAbstractListModel>
#include <QtCore/QTimer>
#include <QtGui/QCloseEvent>
#include <QtGui/QFormLayout>
#include <QtGui/QGroupBox>
#include <QtGui/QPainter>
#include <QtGui/QStyledItemDelegate>
#include <QtGui/QRadioButton>
#include <QtGui/QButtonGroup>

void ChangeLogDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
            const QModelIndex &index ) const
{
    painter->save();

    QStyleOptionViewItemV4 opt4 = option;
    initStyleOption(&opt4, index);

    const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt4.widget);
    bool selected = view->selectionModel()->currentIndex().row() == index.row();

    QColor bgColor;
    bool odd =  index.row() % 2;

    if (selected)
        bgColor = opt4.palette.highlight().color();
    else
        bgColor = (odd) ? opt4.palette.background().color() : QColor("white");

    painter->setBrush(bgColor);
    painter->setPen(Qt::NoPen);
    painter->drawRect(opt4.rect);

    const QAbstractItemModel *model = view->model();
    if (!model)
        return;

    QRect b = opt4.rect;
    b.adjust(2, 2, -4, -4);

    painter->setBrush(Qt::NoBrush);
    painter->setPen(opt4.palette.foreground().color());
    QFontMetrics fm(painter->font());
    int height = fm.height();

    QPoint topLeft = opt4.rect.topLeft();

    painter->drawText(topLeft.x() + 10, topLeft.y() + height,
                      model->data(index, ChangeLogModel::AuthorRole).toString());
    painter->drawText(topLeft.x() + 10, topLeft.y() + 2 * height + 5,
                      model->data(index, ChangeLogModel::TimestampRole).toString());

    FieldMap changedFields = model->data(index, ChangeLogModel::ChangedFieldsRole).value<FieldMap>();

    int i = height;

    QDateTime lastEdited;
    int timesEdited = 0;

    foreach (QString field, changedFields.keys()) {
        if (field == "comment")
            continue;
        if (field.startsWith("_comment") && field.length() > 8) {
            timesEdited++;
            QDateTime edited =
                    QDateTime::fromTime_t(changedFields.value(field).value.toString().left(10).toInt());
            if (edited > lastEdited)
                lastEdited = edited;
            continue;
        }

        QFont old = painter->font();
        QFont boldFont = old;
        boldFont.setBold(true);
        painter->setFont(boldFont);
        painter->drawText(topLeft.x() + 180, topLeft.y() + i, field);
        painter->setFont(old);

        QString newValue;
        FieldValue value = changedFields.value(field);
        if (field == "description")
            newValue = "modified";
        else
            newValue = fm.elidedText(value.value.toString(), Qt::ElideRight,
                                             opt4.rect.width() - 270);

        painter->drawText(topLeft.x() + 280, topLeft.y() + i, newValue);
        i += height;
    }
    if (timesEdited > 0) {
        painter->drawText(topLeft.x() + 180, topLeft.y() + i,
                          QString(tr("The comment was last edited %1")).arg(lastEdited.toString()));
        i += height;
    }
    QString comment = model->data(index, ChangeLogModel::CommentRole).toString();

    QRect textArea(topLeft.x() + 180, topLeft.y() + i,
                   opt4.rect.width() - (topLeft.x() + 180) - 40, opt4.rect.height() - i);

    painter->drawText(textArea, comment);

    painter->restore();
}

QSize ChangeLogDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 opt4 = option;
    initStyleOption(&opt4, index);
    QFontMetrics fm(opt4.font);
    int fontHeight = fm.height();
    QSize size;
    size.setWidth(opt4.rect.width());

    const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt4.widget);
    const ChangeLogModel *model = qobject_cast<ChangeLogModel *>(view->model());

    if (!view || !model)
        return QSize(opt4.rect.width(), 30);

    FieldMap changeFields = model->data(index, ChangeLogModel::ChangedFieldsRole).value<FieldMap>();
    int numFields = changeFields.count();

    int height = 10 + fontHeight * numFields + fontHeight;
    QString comment = model->data(index, ChangeLogModel::CommentRole).toString();
    if (comment.length() > 0) {
        int commentWidthPix = comment.size() * fm.averageCharWidth();
        int rows = qCeil(commentWidthPix / (size.width() - 220));
        int commentAreaHeight = fontHeight * (rows) + 20;
        height += commentAreaHeight;
    }
    size.setHeight(height);
    return size;
}

QVariant ChangeLogModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.model() != this)
        return QVariant();

    if (index.row() > m_entries.count())
        return QVariant();

    TicketChangeset *cs = m_entries.at(index.row());

    if (!cs)
        return QVariant();

    switch (role) {
    case AuthorRole:
        return cs->author();
        break;
    case TimestampRole:
        return cs->timestamp();
        break;
    case ChangedFieldsRole:
        return QVariant::fromValue(cs->changedFields());
        break;
    case CommentRole:
        return cs->changedFields().value("comment").value;
        break;
    }
    return QVariant();
}

int ChangeLogModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_entries.count();
}

void ChangeLogModel::clear()
{
    if (m_entries.count() == 0)
        return;
    beginRemoveRows(QModelIndex(), 0, m_entries.count() - 1);
    m_entries.clear();
    endRemoveRows();
}

void ChangeLogModel::setEntries(QList<TicketChangeset *> newEntries)
{
    m_entries.clear();
    if (newEntries.count() == 0)
        return;

    beginInsertRows(QModelIndex(), 0, newEntries.count() - 1);
    m_entries.append(newEntries);
    endInsertRows();
}

SingleTicketWidget::SingleTicketWidget(Ticket *ticket, TicketExplorer *explorer,
                                       QWidget *parent)
    : QWidget(parent),
      ui(new Ui::SingleTicketWidget),
      m_te(explorer),
      m_ticket(ticket),
      m_currentAction(0),
      m_reloadHistory(false),
      m_periodicalUpdateTimer(new QTimer(this)),
      m_actionFrame(0)
{
    setupForm();
    m_periodicalUpdateTimer->start(30000);
    updateForm();
}

SingleTicketWidget::SingleTicketWidget(TicketExplorer *explorer,
                                       QWidget *parent)
    : QWidget(parent),
      ui(new Ui::SingleTicketWidget),
      m_te(explorer),
      m_ticket(0),
      m_currentAction(0),
      m_reloadHistory(false),
      m_periodicalUpdateTimer(new QTimer(this)),
      m_actionFrame(0)
{
    setupForm();
    ui->lineEditReportedBy->setText(m_te->username());
}


void SingleTicketWidget::setupForm()
{
    ui->setupUi(this);
    ui->detailsButtonEditMain->setText("Details");
    ui->detailsButtonEditMain->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    ui->detailsButtonEditActions->setText("Details");
    ui->detailsButtonEditActions->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
    ui->frameAddComment->setVisible(false);

    ui->csView->setModel(new ChangeLogModel(this));
    ui->csView->setItemDelegate(new ChangeLogDelegate());
    m_infoWidget = new InformationWidget(this);
    ui->baseLayout->insertWidget(0, m_infoWidget);

    connect(m_te, SIGNAL(error(TicketError)), this, SLOT(onError(TicketError)));
    connect(m_te, SIGNAL(changedTickets(QList<int>)),
            this, SLOT(recentlyChangedTicketsListed(QList<int>)));
    connect(m_periodicalUpdateTimer, SIGNAL(timeout()), this, SLOT(periodicalUpdate()));
}

SingleTicketWidget::~SingleTicketWidget()
{
    delete ui;
}

void SingleTicketWidget::loadTicket(Ticket *ticket)
{
    m_ticket = ticket;

    if (!m_ticket) {
        qWarning() << "load ticket: null ticket!";
        return;
    }

    connect(m_ticket, SIGNAL(updated(const Ticket*)),
            this, SLOT(ticketUpdated()), Qt::UniqueConnection);
    connect(m_ticket, SIGNAL(actionsLoaded()), this,
            SLOT(actionsLoaded()), Qt::UniqueConnection);
    connect(m_ticket, SIGNAL(historyLoaded()), this,
            SLOT(historyLoaded()), Qt::UniqueConnection);

    m_ticket->loadAvailableActions();
    m_ticket->loadHistory();

    m_periodicalUpdateTimer->start(30000);
    connect(m_periodicalUpdateTimer, SIGNAL(timeout()), this, SLOT(periodicalUpdate()),
            Qt::UniqueConnection);

    updateForm();
}

void SingleTicketWidget::setFormMode(const FormMode &formMode)
{
    ui->labelCreated->setVisible(formMode);

    QList<QStackedWidget *> stacks = ui->frameMain->findChildren<QStackedWidget *>();
    foreach (QStackedWidget *stack, stacks)
        stack->setCurrentIndex(formMode == NewTicket ? 1 : 0);

    ui->detailsButtonEditMain->setVisible(formMode);
    ui->buttonSaveMainChanges->setVisible(formMode);

    stacks = ui->frameActions->findChildren<QStackedWidget *>();
    foreach (QStackedWidget *stack, stacks)
        stack->setCurrentIndex(formMode == NewTicket ? 1 : 0);

    ui->detailsButtonEditActions->setVisible(formMode);
    ui->buttonSaveActionChanges->setVisible(formMode);

    ui->labelChangeHistoryTitle->setVisible(formMode);
    ui->frameChangeHistory->setVisible(formMode);
    ui->buttonAddNewComment->setVisible(formMode);

    ui->buttonCreateTicket->setVisible(formMode == NewTicket ? 1 : 0);

    if (formMode == NewTicket) {
        this->setWindowTitle("New Ticket");
        ui->labelOwnedBy1->setText("");
        ui->labelOwnedBy2->setText("");
        ui->labelStatus1->setText("NEW");
        ui->labelStatus2->setText("NEW");
        this->resize(this->width(), 450);
        updateForm();
    } else {
        this->resize(this->width(), 800);
        ui->buttonCreateTicket->setVisible(false);
    }
}

void SingleTicketWidget::on_detailsButtonEditMain_clicked()
{
    enableMainEdit(ui->stackedWidgetMain->currentIndex() == 0);
}

void SingleTicketWidget::on_detailsButtonEditActions_clicked()
{
    enableDetailsEdit(ui->stackedWidgetActions->currentIndex() == 0);
}

void SingleTicketWidget::on_buttonAddNewComment_clicked()
{
    ui->labelAddCommentTitle->setText("Add new comment");
    ui->buttonAddNewComment->setVisible(false);
    ui->textEditComment->clear();
    ui->frameAddComment->setVisible(true);
}

void SingleTicketWidget::on_buttonCancel_clicked()
{
    ui->buttonAddNewComment->setVisible(true);
    ui->frameAddComment->setVisible(false);
}

void SingleTicketWidget::on_buttonCreateTicket_clicked()
{
    QString summary = ui->lineEditSummary->text();
    QString description = ui->textEditDescription->toPlainText();

    if (summary.isEmpty()) {
        m_infoWidget->display(tr("You must fill in a ticket summary."), "", 0, 0);
        ui->lineEditSummary->setFocus();
        return;
    } else if (description.isEmpty()) {
        m_infoWidget->display(tr("You must fill in a ticket description."), "", 0, 0);
        ui->textEditDescription->setFocus();
        return;
    }
    m_infoWidget->hide();

    m_ticket = m_te->createTicket();

    lockForm(true);

    QScopedPointer<TicketEditor> editor(m_ticket->editor());
    editor->setFieldByName("reporter", ui->lineEditReportedBy->text());
    editor->setFieldByName("keywords", ui->lineEditKeywords->text());
    editor->setFieldByName("cc", ui->lineEditCc->text());
    editor->setFieldByName("priority", ui->comboBoxPriority->currentText());
    editor->setFieldByName("type", ui->comboBoxType->currentText());
    editor->setFieldByName("summary", summary);
    editor->setFieldByName("description", description);
    editor->save();

    connect(m_ticket, SIGNAL(updated(const Ticket*)),
            this, SLOT(ticketCreated()), Qt::UniqueConnection);
    setFormMode(SingleTicketWidget::EditTicket);
}

void SingleTicketWidget::buttonReply_clicked()
{
    QObject *senderObject = sender();
    QPushButton *senderButton = reinterpret_cast<QPushButton *>(senderObject);

    ui->labelAddCommentTitle->setText("Reply to comment");
    ui->textEditComment->setText(senderButton->property("textToReply").toString());
    ui->buttonAddNewComment->setVisible(false);
    ui->frameAddComment->setVisible(true);
}


void SingleTicketWidget::updateForm()
{
    if (m_ticket) {
        setWindowTitle(m_ticket->fieldByName("summary").toString());

        // Fill the display labels
        ui->labelCreated->setText(m_ticket->created().toString());
        ui->labelReportedBy->setText(m_ticket->fieldByName("reporter").toString());
        ui->labelOwnedBy1->setText(m_ticket->fieldByName("owner").toString());
        ui->labelOwnedBy2->setText(m_ticket->fieldByName("owner").toString());
        ui->labelPriority->setText(m_ticket->fieldByName("priority").toString());
        ui->labelKeywords->setText(m_ticket->fieldByName("keywords").toString());
        ui->labelType->setText(m_ticket->fieldByName("type").toString());
        ui->labelCc->setText(m_ticket->fieldByName("cc").toString());
        ui->labelSummary->setText(m_ticket->fieldByName("summary").toString());
        ui->labelDescription->setText(m_ticket->fieldByName("description").toString());
        ui->labelStatus1->setText(m_ticket->fieldByName("status").toString().toUpper());
        ui->labelStatus2->setText(m_ticket->fieldByName("status").toString().toUpper());

        // Fill the edit mode editors
        ui->lineEditReportedBy->setText(ui->labelReportedBy->text());
        ui->lineEditKeywords->setText(ui->labelKeywords->text());
        ui->lineEditCc->setText(ui->labelCc->text());
        ui->lineEditSummary->setText(ui->labelSummary->text());
        ui->textEditDescription->setText(ui->labelDescription->text());
    }

    QList<TicketField> availableFields = m_te->getAvailableFields();
    ui->comboBoxPriority->clear();
    ui->comboBoxType->clear();
    for (int i = 0; i < availableFields.count(); i++)
        if (availableFields[i].name == "priority" && availableFields[i].hasOptions()) {
            ui->comboBoxPriority->addItems(availableFields[i].options);
            for (int index = 0; index < ui->comboBoxPriority->count(); index++)
                if (ui->labelPriority->text() == ui->comboBoxPriority->itemText(index)) {
                    ui->comboBoxPriority->setCurrentIndex(index);
                    index = ui->comboBoxPriority->count();
                }
        } else if (availableFields[i].name == "type" && availableFields[i].hasOptions()) {
            ui->comboBoxType->addItems(availableFields[i].options);
            for (int index = 0; index < ui->comboBoxType->count(); index++)
                if (ui->labelType->text() == ui->comboBoxType->itemText(index)) {
                    ui->comboBoxType->setCurrentIndex(index);
                    index = ui->comboBoxType->count();
                }
        }
}

void SingleTicketWidget::historyLoaded()
{
    ChangeLogModel *model = qobject_cast<ChangeLogModel *>(ui->csView->model());
    if (model && m_ticket) {
        if (m_ticket->history().count() > 0)
            model->setEntries(m_ticket->history());
    }
}

void SingleTicketWidget::actionsLoaded()
{
    if (!m_ticket)
        return;

    if (m_actionFrame)
        delete m_actionFrame;
    QList<TicketAction *> actions = m_ticket->availableActions();
    m_actionFrame = new QWidget(this);
    m_actionFrame->setLayout(new QVBoxLayout());
    QButtonGroup *buttons = new QButtonGroup(m_actionFrame);
    bool first = true;
    foreach (TicketAction *action, actions) {
        ActionWidget *aw = new ActionWidget(action, buttons, this);
        m_actionFrame->layout()->addWidget(aw);
        // Make sure the first action is set active and checked
        if (first) {
            aw->toggle();
            m_currentAction = action;
            first = false;
        }
        connect(aw, SIGNAL(selected(const TicketAction*,QString)),
                this, SLOT(actionSelected(const TicketAction*,QString)));
    }
    ui->layoutTicketActions->addWidget(m_actionFrame);
}

void SingleTicketWidget::on_buttonApplyComment_clicked()
{
    if (!m_ticket)
        return;
    QScopedPointer<TicketEditor> editor(m_ticket->editor());
    editor->save(ui->textEditComment->toPlainText());
    m_reloadHistory = true;
    lockForm(true);
    ui->textEditComment->clear();
}

void SingleTicketWidget::ticketUpdated()
{
    updateForm();
    if (m_reloadHistory && m_ticket)
        m_ticket->loadHistory();
    unlockForm();
    enableMainEdit(false);
    enableDetailsEdit(false);
}

void SingleTicketWidget::ticketCreated()
{
    updateForm();
    lockForm(false);
    m_periodicalUpdateTimer->start(30000);
}

void SingleTicketWidget::lockForm(bool lock)
{
    foreach (QObject *obj, children()) {
        QWidget *widget = qobject_cast<QWidget *>(obj);
        if (widget && widget != m_infoWidget) {
            if (QLineEdit *le = qobject_cast<QLineEdit *>(widget))
                le->setReadOnly(lock == false);
            else if (QTextEdit *te = qobject_cast<QTextEdit *>(widget))
                te->setReadOnly(lock == false);
            else
                widget->setEnabled(lock == false);
        }
    }
}

void SingleTicketWidget::unlockForm()
{
    lockForm(false);
}

void SingleTicketWidget::on_buttonSaveMainChanges_clicked()
{
    if (!m_ticket)
        return;

    lockForm(true);

    QScopedPointer<TicketEditor> editor(m_ticket->editor());
    editor->setFieldByName("reporter", ui->lineEditReportedBy->text());
    editor->setFieldByName("keywords", ui->lineEditKeywords->text());
    editor->setFieldByName("cc", ui->lineEditCc->text());
    editor->setFieldByName("priority", ui->comboBoxPriority->currentText());
    editor->setFieldByName("type", ui->comboBoxType->currentText());
    editor->setFieldByName("summary", ui->lineEditSummary->text());
    editor->save();
}

void SingleTicketWidget::on_buttonSaveActionChanges_clicked()
{
    if (!m_ticket)
        return;

    lockForm(true);
    if (m_currentAction) {
        QScopedPointer<TicketEditor> editor(m_ticket->editor());
        editor->setFieldByName("description", ui->textEditDescription->toPlainText());
        editor->setFieldByName("action", m_currentAction->action());
        editor->setFieldByName(m_currentAction->inputName(),
                               m_actionValue);
        editor->save();
    }
}

void SingleTicketWidget::recentlyChangedTicketsListed(const QList<int> &tickets)
{
    if (!m_ticket)
        return;
    if (tickets.contains(m_ticket->id())) {
        m_infoWidget->display(tr("Ticket has changed! (Warning! Reloading the ticket will "
                                 "discard all local changes)"),
                              "Reload", this, SLOT(reload()));
        lockForm(true);
    }
}

void SingleTicketWidget::periodicalUpdate()
{
    if (!m_ticket)
        return;
    QDateTime lastMod = m_ticket->modified();
    if (lastMod.isValid())
        m_te->listChangedTickets(lastMod.addMSecs(1000));
    else
        qWarning("Invalid modified time");
}

void SingleTicketWidget::reload()
{
    if (m_ticket) {
        m_ticket->refresh();
        m_reloadHistory = true;
        m_infoWidget->hide();
    }
}

void SingleTicketWidget::onError(const TicketError &error)
{
    lockForm(true);
    m_infoWidget->display(error.error(), "Reload ticket", this, SLOT(reload()), SLOT(unlockForm()));
}

void SingleTicketWidget::closeEvent(QCloseEvent *event)
{
    m_ticket = 0;
    m_periodicalUpdateTimer->stop();
    m_periodicalUpdateTimer->disconnect();
    ChangeLogModel *model = qobject_cast<ChangeLogModel *>(ui->csView->model());
    if (model)
        model->clear();
    event->accept();
}

void SingleTicketWidget::enableMainEdit(bool enable)
{
    ui->stackedWidgetMain->setCurrentIndex((enable) ? 1 : 0);
}

void SingleTicketWidget::enableDetailsEdit(bool enable)
{
    ui->stackedWidgetActions->setCurrentIndex((enable) ? 1 : 0);
}

void SingleTicketWidget::actionSelected(const TicketAction *action, const QString &inputValue)
{
    m_currentAction = action;
    m_actionValue = inputValue;
}

ActionWidget::ActionWidget(const TicketAction *action, QButtonGroup *group, QWidget *parent)
    : QWidget(parent), m_action(action), m_fields(0), m_value(0)
{
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setMargin(0);
    layout->setSpacing(5);
    m_radio = new QRadioButton(action->label(), this);
    m_radio->setToolTip(action->hint());
    connect(m_radio, SIGNAL(toggled(bool)), this, SLOT(radioButtonToggled(bool)));
    group->addButton(m_radio);
    layout->addWidget(m_radio);
    QStringList allowedFields = action->allowedFields();
    // If additional input is requested, create a combo box if we have multiple values the
    // user needs to pick from, otherwise show a regular line edit.
    if (allowedFields.count() > 0) {
        m_fields = new QComboBox(this);
        m_fields->addItems(allowedFields);
        layout->addWidget(m_fields);
        m_radio->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
        connect(m_fields, SIGNAL(activated(QString)), this, SLOT(inputValueChanged()));
    } else if (!action->inputName().isEmpty()) {
            m_radio->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
            m_value = new QLineEdit(action->inputValue(), this);
            layout->addWidget(m_value);
            connect(m_value, SIGNAL(textChanged(QString)), this, SLOT(inputValueChanged()));
    }
    enableDisableWidgets(false);
}

const TicketAction *ActionWidget::action() const
{
    return m_action;
}

void ActionWidget::toggle()
{
    m_radio->toggle();
}

void ActionWidget::radioButtonToggled(bool value)
{
    enableDisableWidgets(value);
    if (value)
        emit selected(m_action, inputValue());
}

QString ActionWidget::inputValue() const
{
    if (m_fields)
        return m_fields->currentText();
    else if (m_value)
        return m_value->text();
    else
        return "";
}

void ActionWidget::enableDisableWidgets(bool enable)
{
    QObjectList objs = children();
    foreach (QObject *obj, objs)
        if (obj != m_radio)
            if (QWidget *widget = qobject_cast<QWidget *>(obj))
                widget->setEnabled(enable);
}

void ActionWidget::inputValueChanged()
{
    // Make sure this is the current action
    if (m_radio->isChecked())
        emit selected(m_action, inputValue());
}


void SingleTicketWidget::on_csView_doubleClicked(const QModelIndex &index)
{
    QVariant val = ui->csView->model()->data(index, ChangeLogModel::ChangedFieldsRole);
    QString author = ui->csView->model()->data(index, ChangeLogModel::AuthorRole).toString();
    if (!val.canConvert<FieldMap>())
        return;

    // This needs some work. The behaviour here is intimately tied to Trac.
    FieldMap fields = val.value<FieldMap>();
    FieldValue commentField = fields.value("comment");
    QString ticketId = commentField.old.toString().split(".").last();
    ui->labelAddCommentTitle->setText("Reply to comment");
    ui->buttonAddNewComment->setVisible(false);
    QString comment;
    QTextStream str(&comment);
    str << "Replying to comment [comment:" << ticketId << " " << author << "]:\n";
    str << "> " << commentField.value.toString() << "\n";
    ui->textEditComment->setText(comment);
    ui->frameAddComment->setVisible(true);
    ui->textEditComment->moveCursor(QTextCursor::End);
    ui->textEditComment->setFocus();
}
